#include <Arduino.h>
#include "LCDLib.h"

#define BLACK 0
#define WHITE 1

unsigned char   revByte[256] = {0x0,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,
0x50,0xd0,0x30,0xb0,0x70,0xf0,0x8,0x88,0x48,0xc8,0x28,0xa8,0x68,0xe8,0x18,0x98,
0x58,0xd8,0x38,0xb8,0x78,0xf8,0x4,0x84,0x44,0xc4,0x24,0xa4,0x64,0xe4,0x14,0x94,
0x54,0xd4,0x34,0xb4,0x74,0xf4,0xc,0x8c,0x4c,0xcc,0x2c,0xac,0x6c,0xec,0x1c,0x9c,
0x5c,0xdc,0x3c,0xbc,0x7c,0xfc,0x2,0x82,0x42,0xc2,0x22,0xa2,0x62,0xe2,0x12,0x92,
0x52,0xd2,0x32,0xb2,0x72,0xf2,0xa,0x8a,0x4a,0xca,0x2a,0xaa,0x6a,0xea,0x1a,0x9a,
0x5a,0xda,0x3a,0xba,0x7a,0xfa,0x6,0x86,0x46,0xc6,0x26,0xa6,0x66,0xe6,0x16,0x96,
0x56,0xd6,0x36,0xb6,0x76,0xf6,0xe,0x8e,0x4e,0xce,0x2e,0xae,0x6e,0xee,0x1e,0x9e,
0x5e,0xde,0x3e,0xbe,0x7e,0xfe,0x1,0x81,0x41,0xc1,0x21,0xa1,0x61,0xe1,0x11,0x91,
0x51,0xd1,0x31,0xb1,0x71,0xf1,0x9,0x89,0x49,0xc9,0x29,0xa9,0x69,0xe9,0x19,0x99,
0x59,0xd9,0x39,0xb9,0x79,0xf9,0x5,0x85,0x45,0xc5,0x25,0xa5,0x65,0xe5,0x15,0x95,
0x55,0xd5,0x35,0xb5,0x75,0xf5,0xd,0x8d,0x4d,0xcd,0x2d,0xad,0x6d,0xed,0x1d,0x9d,
0x5d,0xdd,0x3d,0xbd,0x7d,0xfd,0x3,0x83,0x43,0xc3,0x23,0xa3,0x63,0xe3,0x13,0x93,
0x53,0xd3,0x33,0xb3,0x73,0xf3,0xb,0x8b,0x4b,0xcb,0x2b,0xab,0x6b,0xeb,0x1b,0x9b,
0x5b,0xdb,0x3b,0xbb,0x7b,0xfb,0x7,0x87,0x47,0xc7,0x27,0xa7,0x67,0xe7,0x17,0x97,
0x57,0xd7,0x37,0xb7,0x77,0xf7,0xf,0x8f,0x4f,0xcf,0x2f,0xaf,0x6f,0xef,0x1f,0x9f,
0x5f,0xdf,0x3f,0xbf,0x7f,0xFF};

byte _buffer[2];
byte _buffer_bmp[LCD_ROWS_BUFF][LCD_COL_BYTES];

LCDLib::LCDLib( ): Adafruit_GFX(LCD_COLS_BUFF, LCD_ROWS_BUFF){
	_isBusy = false;
	
	#ifdef USE_SPI_HARDWARE
		cur_vBit = 0;
		this->vbitIntervalMs = VCOM_INTERVAL;
		this->prev_ms = 0;

		//	SET-UP SLAVE SELECT AND DISP_ENABLE PINS
		pinMode(SLAVE_SELECT_PIN, OUTPUT);
		digitalWrite(SLAVE_SELECT_PIN, LOW);
		pinMode(DISP_ENABLE, OUTPUT);

		//	INITIALIZE SPI HARDWARE
			//	PANEL DATA MUST BE SENT IN LITTLE-ENDIAN FORMAT (MSB FIRST)
			//	ARDUINO IS BIG ENDIAN SO IT SENDS DATA FORMAT WITH LSB FIRST
		SPI.setBitOrder(LSBFIRST);
			//	SET-UP CLOCK AND PULSE FOR DATA
		SPI.setDataMode(SPI_MODE0);
			//	SPI CLOCK RUN AT 1 MHz MAX FOR 3.3V
		SPI.setClockDivider(SPI_CLOCK_DIV16);
	#else
	#endif
}

//nothing to destruct		
LCDLib::~LCDLib( ){
	;	
}

void LCDLib::begin( ){
	#ifdef DEBUG_LCD
		Serial.print("\tStarted Sharp Memory LCD: { ");
	#endif
	#ifdef USE_SPI_HARDWARE
		//	START SPI
		SPI.begin(); 
		//Clear the lcd shadow memory (and issue the clear command)
		this->enable();
		this->clear();
	#endif
	#ifdef DEBUG_LCD
		Serial.println("}");
	#endif
}

void LCDLib::enable( ){
	// ENABLE DISPLAY. SHOW LCD MEMORY CONTENT
	digitalWrite(DISP_ENABLE, HIGH);
}

void LCDLib::disable(){
	// DISABLE DISPLAY. HIDE LCD MEMORY CONTENT
	digitalWrite(DISP_ENABLE, LOW);
}

void LCDLib::clear( ){
	//memset(lcdShadow,0xFF,sizeof(lcdShadow));  //first clear shadow ram
	#ifdef USE_SPI_HARDWARE
		//	SEND CLEAR SCREEN COMMAND
		digitalWrite(SLAVE_SELECT_PIN, HIGH);	
		delay(LCD_DELAY);
		//	COMMAND
		SPI.transfer(CMD_CLEAR);
		//	TRAILER
		SPI.transfer(0);
		delay(LCD_DELAY);
		digitalWrite(SLAVE_SELECT_PIN, LOW);
	#endif
}

void LCDLib::blackscr( ){
	char cmdByte = 0;
	int i = 0;
	int j = 0;

	//set the command byte
	cmdByte=CMD_WRITE_ROW;
	//Handle the polarity switch needed by the LCD
	handleVbit(&cmdByte);
	//setup for the SPI write
	digitalWrite(SLAVE_SELECT_PIN, HIGH);	
	delay(LCD_DELAY);
	SPI.transfer(cmdByte);
	for(i = 0; i < LCD_ROWS; i++)
	{
		SPI.transfer(i+1);
		for(j = 0; j < LCD_COL_BYTES; j++)
		{
			SPI.transfer(0);
		}
		//send trailer
		SPI.transfer(0);
	}
	//send final trailer
	SPI.transfer(0);
	//cleanup after the SPI write
	delay(LCD_DELAY);
	digitalWrite(SLAVE_SELECT_PIN, LOW);
}

void LCDLib::logoscr( uint8_t *_data ){
	char cmdByte = 0;
	int i = 0;
	int j = 0;

	//set the command byte
	cmdByte=CMD_WRITE_ROW;
	//Handle the polarity switch needed by the LCD
	handleVbit(&cmdByte);
	//setup for the SPI write
	digitalWrite(SLAVE_SELECT_PIN, HIGH);	
	delay(LCD_DELAY);
	SPI.transfer(cmdByte);
	for(i = 0; i < LCD_ROWS; i++)
	{
		SPI.transfer(i+1);
		for(j = 0; j < LCD_COL_BYTES; j++)
		{
			SPI.transfer(~revByte[pgm_read_byte(_data++)]);
			delayMicroseconds(LCD_TX_DELAY);
		}
		//send trailer
		SPI.transfer(0);
		delayMicroseconds(LCD_TX_DELAY);
	}
	//send final trailer
	SPI.transfer(0);
	//cleanup after the SPI write
	delay(LCD_DELAY);
	digitalWrite(SLAVE_SELECT_PIN, LOW);
}

void LCDLib::bufferscr( uint8_t *_data , uint8_t _pos_line , uint8_t _pos_line_end ){
	char cmdByte = 0;
	int i = 0;
	int j = 0;

	//set the command byte
	cmdByte=CMD_WRITE_ROW;
	//Handle the polarity switch needed by the LCD
	handleVbit(&cmdByte);
	//setup for the SPI write
	digitalWrite(SLAVE_SELECT_PIN, HIGH);	
	delay(LCD_DELAY);
	SPI.transfer(cmdByte);
	for(i = _pos_line; i < _pos_line_end; i ++)
	{
		SPI.transfer(i+1);
		for(j = 0; j < LCD_COL_BYTES; j++)
		{
			SPI.transfer(~revByte[pgm_read_byte(_data++)]);
			delayMicroseconds(LCD_TX_DELAY);
		}
		//send trailer
		SPI.transfer(0);
		delayMicroseconds(LCD_TX_DELAY);
	}
	//send final trailer
	SPI.transfer(0);
	//cleanup after the SPI write
	delay(LCD_DELAY);
	digitalWrite(SLAVE_SELECT_PIN, LOW);
}

void LCDLib::bufferbmpscr( uint8_t _pos_line , uint8_t _pos_line_end ){
	char cmdByte = 0;
	int i = 0;
	int j = 0;

	//set the command byte
	cmdByte=CMD_WRITE_ROW;
	//Handle the polarity switch needed by the LCD
	handleVbit(&cmdByte);
	//setup for the SPI write
	digitalWrite(SLAVE_SELECT_PIN, HIGH);	
	delay(LCD_DELAY);
	SPI.transfer(cmdByte);
	for(i = _pos_line; i < _pos_line_end; i++)
	{
		SPI.transfer(i+1);
		for(j = 0; j < LCD_COL_BYTES; j++)
		{
			SPI.transfer(_buffer_bmp[i-_pos_line][j]);
			delayMicroseconds(LCD_TX_DELAY);
		}
		//send trailer
		SPI.transfer(0);
		delayMicroseconds(LCD_TX_DELAY);
	}
	//send final trailer
	SPI.transfer(0);
	//cleanup after the SPI write
	delay(LCD_DELAY);
	digitalWrite(SLAVE_SELECT_PIN, LOW);
}

void LCDLib::printbmpbuffer(int i, int j, prog_uchar* imgBuff, int size_i, int size_j){
	unsigned char thisBmpByte;
	unsigned char lastBmpByte;
	unsigned char thisMaskByte;
	unsigned char offset;

	unsigned int iIndex = 0;
	unsigned char jByte = j/8;
	unsigned char jByteCount;
	unsigned char jByteIndex;

	for(iIndex = 0; iIndex < size_i; iIndex++)
	{
		offset = j%8;
		jByteCount = size_j / 8; //integer divide with round up
		if( size_j % 8 != 0)
		{
			jByteCount++;
		}
		if(offset == 0)
		{
			for(jByteIndex=0;jByteIndex < jByteCount;jByteIndex++)
			{
				thisBmpByte = pgm_read_byte_near(imgBuff+(iIndex*jByteCount)+jByteIndex);
				thisBmpByte = ~thisBmpByte;
				if((i+iIndex>=0) && (i+iIndex<LCD_ROWS))
				{
					if((jByte+jByteIndex>=0) && (jByte+jByteIndex < LCD_COL_BYTES))
					{
						_buffer_bmp[i+iIndex][jByte+jByteIndex] = revByte[thisBmpByte];
					}
				}
			}
		}
		else
		{
			//first byte is special.  do it outside the loop
			thisBmpByte = pgm_read_byte_near(imgBuff+iIndex*jByteCount);
			thisBmpByte = thisBmpByte >> offset;  //move the first byte over
			thisMaskByte = (unsigned char)0xFF >> offset;   		//build a mask;
			if((i+iIndex>=0) && (i+iIndex<LCD_ROWS))
			{
				if((jByte>=0) && (jByte < LCD_COL_BYTES))
				{			
					_buffer_bmp[i+iIndex][jByte] |= revByte[thisMaskByte];
					thisBmpByte = ~thisBmpByte;
					//shifted in 0's become 1's (the background color.
					_buffer_bmp[i+iIndex][jByte] &= revByte[thisBmpByte];  //set the bits in this byte
				}
			}

			for(jByteIndex=1;jByteIndex < jByteCount;jByteIndex++)
			{				
				//first deal with pixels left over from the last character
				//set up the bits in the first bmp byte
				lastBmpByte = pgm_read_byte_near(imgBuff+(iIndex*jByteCount)+(jByteIndex-1));
				lastBmpByte = lastBmpByte << (8 - offset);  
								
				//set up the bits in the second bmp byte (shift and mask)
				thisBmpByte = pgm_read_byte_near(imgBuff+(iIndex*jByteCount)+jByteIndex);
				thisBmpByte = thisBmpByte >> offset; 
				
				//now put it all together
				thisBmpByte = thisBmpByte | lastBmpByte;
				//Invert the colors (for correct display on sharp memory lcd)
				thisBmpByte = ~thisBmpByte; 
				//write it to the shadow (destroy any extra bits to the right)
				if((i+iIndex>=0) && (i+iIndex<LCD_ROWS))
				{
					if((jByte+jByteIndex>=0) && (jByte+jByteIndex < LCD_COL_BYTES))
					{				
						_buffer_bmp[i+iIndex][jByte+jByteIndex] = revByte[thisBmpByte];
					}
				}
				//first deal with pixels left over from the last character
			}
			//Last Byte is special - do this outside the loop
			//set up the bits in the second bmp byte
			thisBmpByte = pgm_read_byte_near(imgBuff+(iIndex*jByteCount)+jByteCount-1);
			thisBmpByte = ~thisBmpByte;
			thisBmpByte = thisBmpByte << (8 - offset);  
			thisBmpByte |= thisMaskByte;
			if((i+iIndex>=0) && (i+iIndex<LCD_ROWS))
			{
				if((jByte+jByteCount>=0) && (jByte+jByteCount < LCD_COL_BYTES))
				{			
					_buffer_bmp[i+iIndex][jByte+jByteCount] = revByte[thisBmpByte]; //use the mask
				}
			}
		}
	}
}

void LCDLib::printbuffer (uint8_t _pos_line , uint8_t _pos_line_end){
	displaybuffer(_pos_line, _pos_line_end);
}

void LCDLib::handleVbit(char *cmd){
	if(millis() - this->prev_ms >= this->vbitIntervalMs) {
		this->prev_ms = millis();
		if(cur_vBit == 0){
			cur_vBit = 1;
		   *cmd = *cmd | V_HIGH_BIT;
		}
		else{
			cur_vBit = 0;
		   *cmd = *cmd & V_BIT_MASK;
		}
	}
}

void LCDLib::displaybuffer( uint8_t _pos_line , uint8_t _pos_line_end ){
	char cmdByte = 0;
	int i = 0;
	int j = 0;
	uint16_t cont = 0;

	//set the command byte
	cmdByte=CMD_WRITE_ROW;
	//Handle the polarity switch needed by the LCD
	handleVbit(&cmdByte);
	//setup for the SPI write
	digitalWrite(SLAVE_SELECT_PIN, HIGH);	
	delay(LCD_DELAY);
	SPI.transfer(cmdByte);
	for(i = _pos_line; i < _pos_line_end; i++)
	{
		SPI.transfer(i+1);
		for(j = 0; j < LCD_COL_BYTES; j++)
		{
			SPI.transfer(_buffer[cont]);
			delayMicroseconds(LCD_TX_DELAY);
			cont ++;
		}
		//send trailer
		SPI.transfer(0);
		delayMicroseconds(LCD_TX_DELAY);
	}
	//send final trailer
	SPI.transfer(0);
	//cleanup after the SPI write
	delay(LCD_DELAY);
	digitalWrite(SLAVE_SELECT_PIN, LOW);
}

void LCDLib::clearBuffer(){
	memset(_buffer_bmp, 0xff, sizeof(_buffer_bmp));
}

void LCDLib::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((x >= LCD_COLS_BUFF) || (y >= LCD_ROWS_BUFF))
    return;

  if (color)
    _buffer[(y*LCD_COLS_BUFF + x) /8] |= (1 << x % 8);
  else
    _buffer[(y*LCD_COLS_BUFF + x) /8] &= ~(1 << x % 8);
}

void LCDLib::printtext (char* _text, uint8_t _cursor_x, uint8_t _cursor_y, uint8_t _size, uint8_t _pos){
	clearBuffer();
	setCursor (_cursor_x, _cursor_y);
	setTextSize(_size);
	setTextColor(BLACK, WHITE);
    print(_text);
	displaybuffer (_pos, _pos+24);
}